// 物理内存分配器，用于用户进程、内核堆栈、页表页、管道缓冲区。
// 分配的页面大小是 4096 bytes。

#include "arch/loongarch.h"
#include "config/type.h"
#include "lib/string.h"
#include "lib/stdout.h"
#include "mm/phylayout.h"
#include "sched/spinlock.h"

void freerange(void *pa_start, void *pa_end);

struct run {
	struct run *next;
};

struct {
	struct spinlock lock;
	struct run *freelist;
} kmem;

void kinit() {
	initlock(&kmem.lock, "kmem");
	freerange((void *)RAMBASE, (void *)RAMSTOP);
}

void freerange(void *pa_start, void *pa_end) {
	char *p;
	pr_debug(freerange, "[%p, %p]", pa_start, pa_end);
	p = (char *)PGROUNDUP((uint64)pa_start);
	for (; p + PGSIZE <= (char *)pa_end; p += PGSIZE)
		kfree(p);
}

// 释放 v 所指向的物理内存页，该页通常应该由调用 kalloc() 返回。 
// (初始化分配器时为例外，见上面的kinit)
void kfree(void *pa) {
	struct run *r;

	if (((uint64)pa % PGSIZE) != 0 || (uint64)pa < RAMBASE || (uint64)pa >= RAMSTOP)
		panic(kfree, "illegal physical address: %p", pa);

	// 填充垃圾值，以便捕捉空引用
	memset(pa, 1, PGSIZE);

	// 释放的物理内存接在链表头
	r = (struct run *)pa;

	acquire(&kmem.lock);
	r->next = kmem.freelist;
	kmem.freelist = r;
	release(&kmem.lock);
}

// 分配一个 4096 字节的物理内存页，返回一个内核可以使用的指针。
// 如果内存不能被分配，则返回 0。
void *kalloc(void) {
	struct run *r;
	// show_free_memory();

	acquire(&kmem.lock);
	r = kmem.freelist;
	if (r)
		kmem.freelist = r->next;
	release(&kmem.lock);

	if (r)
		memset((char *)r, 5, PGSIZE); // 填充垃圾值

	return (void *)r;
}

void show_kmem_free() {
	// DEBUG, 只展示列表头几条
	struct run *r = kmem.freelist;
	int n = 3;
	pr_debug(show_free_memory, "freelist: ");
	while (r && n--) {
		pr_debug(show_free_memory, "\t%p", (uint64)r);
		r = r->next;
	}
}

void kmemdump() {
	int unused_pages = 0;
	int all_pages = (RAMSTOP - RAMBASE) / PGSIZE;
	struct run *r = kmem.freelist;
	while (r) {
		unused_pages++;
		r = r->next;
	}
	
	int used_pages = (all_pages-unused_pages);
	int usage = (used_pages * 100)/all_pages;
	printk("\t\ttotal\tused\tfree\tusage\n");
	printk("Memory:\t\t%d\t%d\t%d\t%d%%\n", all_pages, used_pages, unused_pages, usage);
}
